package cn.fang2chen.distributed.lock.spring;

import cn.fang2chen.java.distributed.lock.framework.*;
import cn.fang2chen.java.distributed.lock.framework.impl.DistributedMutex;
import cn.fang2chen.java.distributed.lock.framework.impl.DistributedReentrantLock;
import cn.fang2chen.java.distributed.lock.framework.impl.DistributedTokenLeasing;
import cn.fang2chen.java.distributed.lock.framework.redis.RedisClient;
import cn.fang2chen.java.distributed.lock.framework.redis.RedisRemoteLockSynchronizer;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;

import java.util.Collections;
import java.util.concurrent.TimeUnit;

/**
 * 分布式锁的自启动器
 *
 * @author dcmini chen
 * @since 1.0.0
 */
@Configuration
@ConditionalOnProperty(name = "distributed.lock.enabled", havingValue = "true", matchIfMissing = true)
@EnableConfigurationProperties(DistributedLockProperties.class)
@AutoConfigureAfter(RedisAutoConfiguration.class)
public class DistributedLockAutoConfinguration {

    private final DistributedLockProperties distributedLockProperties;

    public DistributedLockAutoConfinguration(final DistributedLockProperties distributedLockProperties) {
        this.distributedLockProperties = distributedLockProperties;
    }

    /**
     * 远程锁
     */
    @Bean
    @ConditionalOnBean(StringRedisTemplate.class)
    @ConditionalOnMissingBean(RemoteLockSynchronizer.class)
    public RemoteLockSynchronizer remoteLockSynchronizer(StringRedisTemplate stringRedisTemplate) {

        return new PollingAcquireInterruptiblyRemoteLockSynchronizer(
                new PollingAcquireRemoteLockSynchronizer(
                        new PollingTryAcquireRemoteLockSynchronizer(
                                new RedisRemoteLockSynchronizer(
                                        new RedisTemplateLockClient(stringRedisTemplate)
                                ),
                                distributedLockProperties.getPollingIntervalNano()),
                        distributedLockProperties.getPollingIntervalNano()),
                distributedLockProperties.getPollingIntervalNano());
    }

    @Bean
    @ConditionalOnBean(RemoteLockSynchronizer.class)
    @ConditionalOnMissingBean(MutexFactory.class)
    @ConditionalOnProperty(name = "distributed.lock.mutexConfig", havingValue = "true", matchIfMissing = true)
    public MutexFactory mutexFactory(RemoteLockSynchronizer remoteLockSynchronizer) {
        return new MutexFactory(distributedLockProperties.getMutexConfig().getLockCacheExpired(), new DistributedMutex(new RemoteLockParam(), remoteLockSynchronizer));
    }

    @Bean
    @ConditionalOnBean(RemoteLockSynchronizer.class)
    @ConditionalOnMissingBean(ReentrantLockFactory.class)
    @ConditionalOnProperty(name = "distributed.lock.reentrantLockConfig", havingValue = "true", matchIfMissing = true)
    public ReentrantLockFactory reentrantLockFactory(RemoteLockSynchronizer remoteLockSynchronizer) {
        return new ReentrantLockFactory(distributedLockProperties.getMutexConfig().getLockCacheExpired(), new DistributedReentrantLock(new RemoteLockParam(), remoteLockSynchronizer));
    }

    @Bean
    @ConditionalOnBean(RemoteLockSynchronizer.class)
    @ConditionalOnMissingBean(TokenLeasingFactory.class)
    @ConditionalOnProperty(name = "distributed.lock.tokenLeasingConfig", havingValue = "true", matchIfMissing = true)
    public TokenLeasingFactory tokenLeasingFactory(RemoteLockSynchronizer remoteLockSynchronizer) {
        return new TokenLeasingFactory(distributedLockProperties.getTokenLeasingConfig().getLockCacheExpired(), new DistributedTokenLeasing("", remoteLockSynchronizer));
    }



    private static class RedisTemplateLockClient implements RedisClient {

        private static final Long SUCCESS = 1L;

        private final String script_lock = "if redis.call('setNx',KEYS[1],ARGV[1]) then if redis.call('get',KEYS[1])==ARGV[1] then return redis.call('PEXPIRE',KEYS[1],ARGV[2]) else return 0 end end";
        private final String script_unlock = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";

        private final StringRedisTemplate redisTemplate;

        public RedisTemplateLockClient(final StringRedisTemplate redisTemplate) {
            this.redisTemplate = redisTemplate;
        }

        @Override
        public boolean setNxPx(String key, String value, long expired) {
            return redisTemplate.opsForValue().setIfAbsent(key, value, expired, TimeUnit.MILLISECONDS);
        }

        @Override
        public boolean delCas(String key, String expect) {
            RedisScript<Long> redisScript = new DefaultRedisScript<>(script_unlock, Long.class);
            Object result = redisTemplate.execute(redisScript, Collections.singletonList(key),expect);
            return SUCCESS.equals(result);
        }

        @Override
        public boolean hasValue(String key) {
            return redisTemplate.hasKey(key);
        }

        @Override
        public String getValue(String key) {
            return redisTemplate.opsForValue().get(key);
        }

        @Override
        public long timeRemaining(String key) {
            return redisTemplate.getExpire(key, TimeUnit.MILLISECONDS);
        }

    }
    

}
